package SC12Project.Milestone1.Warehouse;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

/**
 * Service provider for a Warehouse.
 * 
 * @author Diego Havenstein
 */
public class WarehouseProvider
    implements WarehouseWS
{

    private String fn = "../datasource/ds_2_2.xml";

    private Unmarshaller u;

    private WareHouse wl;

    /**
     * Constructor
     * 
     * @throws FileNotFoundException
     */
    public WarehouseProvider()
        throws FileNotFoundException
    {
        parseAndInit( fn );
    }

    void parseAndInit( String path )
        throws FileNotFoundException
    {

        try
        {
            ClassLoader cl = SC12Project.Milestone1.Warehouse.ObjectFactory.class.getClassLoader();
            String packageName = WareHouse.class.getPackage().getName();
            JAXBContext jc = JAXBContext.newInstance( packageName, cl );
            this.u = jc.createUnmarshaller();
            @SuppressWarnings( "unchecked" )
            JAXBElement<WareHouse> root = (JAXBElement<WareHouse>) u.unmarshal( new FileInputStream( fn ) );
            wl = root.getValue();

        }
        catch ( JAXBException e )
        {
            e.printStackTrace();
        }

    }

    /**
     * Query the amount of available items in the warehouse by resourceID
     * 
     * @param resourceID the resourceID of the item in the warehouse
     * @return the amount of the item in the warehouse
     */
    @Override
    public int query( String resourceID )
    {
        int n = wl.getItems().getItem().size();
        for ( int i = 0; i < n; i++ )
            if ( wl.getItems().getItem().get( i ).getResourceID().equals( resourceID ) )
                return wl.getItems().getItem().get( i ).getAmount();
        return 0;
    }

    /**
     * Remove the amount of items from the warehouse
     * 
     * @param resourceID the resourceID of the item in the warehouse
     * @param amount the amount of items need to be picked up
     * @return true if the operation is successfull; otherwise false
     * @exception NotEnoughItemException
     */
    @Override
    public boolean pickupItems( String resourceID, int amount )
        throws NotEnoughItemException
    {
        int n = wl.getItems().getItem().size();
        for ( int i = 0; i < n; i++ )
            if ( wl.getItems().getItem().get( i ).getResourceID().equals( resourceID ) )
                if ( amount <= wl.getItems().getItem().get( i ).getAmount() )
                {
                    try
                    {
                        wl.getItems().getItem().get( i ).setAmount( wl.getItems().getItem().get( i ).getAmount()
                                                                        - amount );
                        marshalIntoXMLFile();
                    }
                    catch ( FileNotFoundException e )
                    {
                        e.printStackTrace();
                    }
                    catch ( JAXBException e )
                    {
                        e.printStackTrace();
                    }
                    return true;
                }
                else
                    throw new NotEnoughItemException();
        return false;
    }

    /**
     * Complement the amount of items to the warehouse
     * 
     * @param resourceID the resourceID of the item in the warehouse
     * @param amount the amount of items need to complement
     * @return the new amount of items in the warehouse
     */
    @Override
    public int complementStock( String resourceID, int amount )
    {
        int n = wl.getItems().getItem().size();
        for ( int i = 0; i < n; i++ )
            if ( wl.getItems().getItem().get( i ).getResourceID().equals( resourceID ) )
            {
                wl.getItems().getItem().get( i ).setAmount( wl.getItems().getItem().get( i ).getAmount() + amount );
                try
                {
                    marshalIntoXMLFile();
                }
                catch ( FileNotFoundException e )
                {

                    e.printStackTrace();
                }
                catch ( JAXBException e )
                {

                    e.printStackTrace();
                }
                return wl.getItems().getItem().get( i ).getAmount();
            }
        // couldn't find item with this resourceID. Create new entry in items list with initial amount amount.
        ItemInfo ii = new ItemInfo();
        ii.setAmount( amount );
        ii.setResourceID( resourceID );
        wl.getItems().getItem().add( ii );

        try
        {
            marshalIntoXMLFile();
        }
        catch ( FileNotFoundException e )
        {
            e.printStackTrace();
        }
        catch ( JAXBException e )
        {
            e.printStackTrace();
        }

        return amount;
    }

    /**
     * Hold some items for later use. The holding items are invisible to the query operation. The items can only be
     * accessed by the ID of the holding request.
     * 
     * @param resourceID the resourceID of the item in the warehouse
     * @param amount the amount of items need to be held
     * @return the ID of the holding request (the ID can be used to pickup items or cancel the holding)
     */
    @Override
    public String holdItems( String resourceID, int amount )
        throws NotEnoughItemException
    {
        int index = -1;
        for ( int i = 0; i < wl.getItems().getItem().size(); i++ )
            if ( wl.getItems().getItem().get( i ).getResourceID().equals( resourceID ) )
                index = i;
        if ( index == -1 || amount > wl.getItems().getItem().get( index ).getAmount() )
            throw new NotEnoughItemException();
        else
        {
            Double id = getGaussian( 100, 10 );
            String holdingID =
                id.toString() + "-" + ( new SimpleDateFormat( "yyyy/MM/dd-HH:mm:ss" ) ).format( new Date() ).toString();
            pickupItems( resourceID, amount );
            HoldingRequestInfo hri = new HoldingRequestInfo();
            ItemInfo ii = new ItemInfo();
            ii.setAmount( amount );
            ii.setResourceID( resourceID );
            hri.setItem( ii );
            hri.setRequestID( holdingID );
            wl.holdingReques.getRequest().add( hri );
            try
            {
                marshalIntoXMLFile();
            }
            catch ( FileNotFoundException e )
            {
                e.printStackTrace();
            }
            catch ( JAXBException e )
            {
                e.printStackTrace();
            }
            return holdingID;
        }
    }

    /**
     * Cancel the holding request. The holding items are released.
     * 
     * @param holdingID the ID of the holding request
     */
    @Override
    public void cancelHoldingItems( String holdingID )
    {
        boolean cancelled = false;
        for ( int i = 0; i < wl.getHoldingReques().getRequest().size() && !cancelled; i++ )
            if ( wl.getHoldingReques().getRequest().get( i ).getRequestID().equals( holdingID ) )
            {
                // return items to warehouse
                complementStock( wl.getHoldingReques().getRequest().get( i ).getItem().getResourceID(),
                                 wl.getHoldingReques().getRequest().get( i ).getItem().getAmount() );
                // delete holding request
                wl.getHoldingReques().getRequest().remove( i );
                try
                {
                    marshalIntoXMLFile();
                }
                catch ( FileNotFoundException e )
                {
                    e.printStackTrace();
                }
                catch ( JAXBException e )
                {
                    e.printStackTrace();
                }
                cancelled = true;
            }
        // would love to throw InvalidHoldingIDException here, but it is not allowed in provided interface ...
    }

    /**
     * Pick up the holding items. Remove holding items from the warehouse
     * 
     * @param holdingID the ID of the holding request
     * @return true if the operation is successful; otherwise false
     * @exception the holdingID is invalid
     */
    @Override
    public boolean pickupHoldingItems( String holdingID )
        throws InvalidHoldingIDException
    {
        for ( int i = 0; i < wl.getHoldingReques().getRequest().size(); i++ )
            if ( wl.getHoldingReques().getRequest().get( i ).getRequestID().equals( holdingID ) )
            {
                wl.getHoldingReques().getRequest().remove( i );
                try
                {
                    marshalIntoXMLFile();
                }
                catch ( FileNotFoundException e )
                {
                    e.printStackTrace();
                }
                catch ( JAXBException e )
                {
                    e.printStackTrace();
                }
                return true;
            }
        throw new InvalidHoldingIDException();
    }

    static double getGaussian( double mean, double variance )
    {
        return mean + ( new Random() ).nextGaussian() * variance;
    }

    /**
     * Method marshals wl object into f
     * 
     * @throws JAXBException
     * @throws FileNotFoundException
     */
    void marshalIntoXMLFile()
        throws JAXBException, FileNotFoundException
    {
        ClassLoader cl = SC12Project.Milestone1.Warehouse.ObjectFactory.class.getClassLoader();
        String packageName = WareHouse.class.getPackage().getName();
        JAXBContext jc = JAXBContext.newInstance( packageName, cl );
        Marshaller m = jc.createMarshaller();
        m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
        ObjectFactory obf = new ObjectFactory();
        JAXBElement<WareHouse> output = obf.createWarehouse( wl );
        m.marshal( output, new FileOutputStream( fn ) );
    }

}
